/**
 * CommunicationLocalImpl.java
 *
 * @author Beatrice Bacelli
 * @author Luca Bedogni
 * @author Silvia Righini
 */
package unibo.lsb.communication;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import unibo.lsb.exception.TokioException;
import unibo.lsb.graphic.GameTableWindowSingleton;
import unibo.lsb.logic.CoveredDice;
import unibo.lsb.logic.LastAssignedPenalty;
import unibo.lsb.logic.Player;
import unibo.lsb.logic.PlayerId;
import unibo.lsb.logic.PlayerSelfSingleton;
import unibo.lsb.logic.PlayersTable;
import unibo.lsb.server.ConfigurationConstants;

public class CommunicationLocal {

	private static final int PENALTY = 0;
	private static final int STATUS = 1;
	private static final int TURN = 2;
	private static final int MESSAGE = 3;
	private static final int WINNING = 4;

	/**
	 * Sends an object to everyone
	 * 
	 * @param code
	 * @param playerNumber
	 * @param alive
	 * @param lastAssignedPenalty
	 * @return
	 * @throws TokioException
	 */

	private static boolean generalNotification(int code, String msg,
			int playerNumber, boolean alive,
			LastAssignedPenalty lastAssignedPenalty,
			List<PlayerId> listOfExcludedPlayers) throws TokioException {

		boolean b = false;
		List<PlayerId> PlayersList = InformationSingleton.getInstance()
				.getGame().getListOfPlayers();
		Iterator<PlayerId> it = PlayersList.iterator();
		PlayerId p = null;

		while (it.hasNext()) {
			p = it.next();
			if (!listContainsPlayer(listOfExcludedPlayers, p)) {
				try {
					singleGeneralNotification(p, code, msg, playerNumber, b,
							lastAssignedPenalty);
				} catch (Exception e) {
				}
			}
		}
		return b;
	}

	private static boolean listContainsPlayer(List<PlayerId> list, PlayerId p) {
		boolean b = false;
		if (list != null) {
			Iterator<PlayerId> ite = list.iterator();
			while (ite.hasNext() && !b) {
				if (p.equals(ite.next())) {
					b = true;
				}
			}
		}
		return b;
	}

	public static boolean sendMessage(String msg) {
		boolean b = false;
		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());
		try {
			b = generalNotification(MESSAGE, msg, -1, false, null, excludedList);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	public static boolean sendMessage(String msg, PlayerId p) {
		boolean b = false;
		try {
			b = singleGeneralNotification(p, MESSAGE, msg, -1, false, null);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	/**
	 * Sends a message to all players EXCEPT the players listed in excludedList
	 * AND the player itself.
	 * 
	 * @param msg
	 * @param excludedList
	 * @return
	 */
	public static boolean sendMessage(String msg, List<PlayerId> excludedList) {
		boolean b = false;
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());
		try {
			b = generalNotification(MESSAGE, msg, -1, false, null, excludedList);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	private static boolean singleGeneralNotification(PlayerId p, int code,
			String msg, int playerNumber, boolean alive,
			LastAssignedPenalty lastAssignedPenalty) throws TokioException {

		CommunicationRemote comServer = null;
		PlayersTable pt = InformationSingleton.getInstance().getpt();
		Player player = pt.getPlayer(pt.getTurnForPlayer(p));
		boolean b = false;
		if (player.isAlive()) {

			// System.out.println("singleGeneralNotification. Player "
			// + p.toString() + ", code = " + code + ", alive = " + alive
			// + ", playerNumber = " + playerNumber);

			try {
				String objectURL = "rmi://" + p.getIpAddress() + ":"
						+ p.getPort() + "/"
						+ ConfigurationConstants.INFORMATIONSERVER_NAME;
				comServer = (CommunicationRemote) Naming.lookup(objectURL);
				switch (code) {
				case PENALTY:
					b = comServer.receivePenalty(lastAssignedPenalty);
					break;
				case STATUS:
					b = comServer.receiveStatus(playerNumber, alive);
					if (!alive) {
					}
					break;
				case TURN:
					b = comServer.receiveTurn(playerNumber);
					break;
				case MESSAGE:
					b = comServer.receiveMsg(msg);
					break;
				case WINNING:
					b = comServer.receiveWinning(playerNumber);
					break;
				}
			} catch (MalformedURLException e) {
				// e.printStackTrace();
				throw new TokioException("I01: Bad url", "URL Error");
			} catch (RemoteException e) {
				// e.printStackTrace();
				throw new TokioException("I02: Error connecting",
						"ConnectionError");
			} catch (NotBoundException e) {
				// e.printStackTrace();
				throw new TokioException("I03: Error connecting",
						"ConnectionError");
			} catch (Exception e) {
				return b;
			}
			if (comServer == null) {
				System.err.println("I04: Error: InformationServer is null");
				throw new TokioException("I04: Error connecting",
						"ConnectionError");
			}
		}
		return b;
	}

	/**
	 * Sends a LastAssignedPenalty to everyone
	 * 
	 * @param lastAssignedPenalty
	 * @return
	 * @throws TokioException
	 */
	public static boolean notifyPenalty(LastAssignedPenalty lastAssignedPenalty) {

		boolean b = false;
		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());
		try {
			b = generalNotification(PENALTY, "", -1, false,
					lastAssignedPenalty, excludedList);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;

	}

	public static boolean notifyStatus(int playerNumber, boolean alive) {
		boolean b = false;
		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());
		try {
			b = generalNotification(STATUS, "", playerNumber, alive, null,
					excludedList);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	/**
	 * Tells all the players but myself who owns the turn
	 */
	public static boolean notifyTurn(int playerNumber) throws TokioException {
		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());

		return generalNotification(TURN, "", playerNumber, false, null,
				excludedList);

	}

	public static boolean notifyTurnToAllBut(PlayerId playerId) {
		int n = InformationSingleton.getInstance().getpt().getTurnForPlayer(
				playerId);
		boolean b = false;

		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(playerId);

		try {
			b = generalNotification(TURN, "", n, false, null, excludedList);
		} catch (TokioException e) {

			e.printStackTrace();
		}
		return b;
	}

	public static boolean notifyWinning(int playerNumber) {
		boolean b = false;
		List<PlayerId> excludedList = new ArrayList<PlayerId>();
		excludedList.add(PlayerSelfSingleton.getInstance().getSelf());
		try {
			b = generalNotification(WINNING, "", playerNumber, false, null,
					excludedList);
		} catch (TokioException e) {
			e.printStackTrace();
		}
		return b;
	}

	/**
	 * Ok it also sends to the same player who sends it.
	 * 
	 * @param playerId
	 * @return
	 */
	public static boolean notifyTurnToAll(PlayerId playerId) {
		int n = InformationSingleton.getInstance().getpt().getTurnForPlayer(
				playerId);
		boolean b = false;

		try {
			b = generalNotification(TURN, "", n, false, null, null);
		} catch (TokioException e) {

			e.printStackTrace();
		}
		return b;
	}

	/**
	 * 
	 * @param coveredDice
	 * @param player
	 *            Player to send coveredDice to
	 * @return
	 * @throws TokioException
	 */
	public static boolean sendDice(CoveredDice coveredDice, Player player)
			throws TokioException {
		boolean b = false;
		CommunicationRemote comServer;
		int myTurn = PlayerSelfSingleton.getInstance().getSelf()
				.getIndexInTable();

		try {
			if (player == null) {
				// TODO gestire il caso in cui tutti siano morti e io abbia
				// vinto per questa ragione
				// System.out.println("Ho vinto io! sono l'unico!");
			} else {
				String objectURL = "rmi://"
						+ player.getPlayerId().getIpAddress() + ":"
						+ player.getPlayerId().getPort() + "/"
						+ ConfigurationConstants.INFORMATIONSERVER_NAME;
				comServer = (CommunicationRemote) Naming.lookup(objectURL);
				b = comServer.receiveDice(coveredDice);

			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
			throw new TokioException("I04: Bad url", "URL Error");
		} catch (RemoteException e) {
			e.printStackTrace();
			throw new TokioException("I05: Error connecting", "ConnectionError");
		} catch (NotBoundException e) {
			e.printStackTrace();
			throw new TokioException("I06: Error connecting", "ConnectionError");
		}
		return b;
	}

	/**
	 * Create a thread that verifies the liveness of next player.
	 * 
	 * Polling the next alive player: if it dies - set dead in one's own
	 * PlayersTable, notify the death to everyone, then start to polling the
	 * next one.
	 * 
	 * when all others players are dead close the game
	 */
	public static void executePollingAll() {

		PlayersTable pt = InformationSingleton.getInstance().getpt();
		List<Player> playerList = pt.getPlayers();
		Iterator<Player> it = playerList.iterator();
		PlayerId self = PlayerSelfSingleton.getInstance().getSelf();

		while (it.hasNext()) {

			final Player player = it.next();
			final PlayerId playerId = player.getPlayerId();

			if (!self.equals(playerId)) {

				Thread t = new Thread() {
					@Override
					public void run() {
						CommunicationRemote commRemote = null;
						PlayersTable pt = InformationSingleton.getInstance()
								.getpt();

						String objectURL = "rmi://" + playerId.getIpAddress()
								+ ":" + playerId.getPort() + "/"
								+ ConfigurationConstants.INFORMATIONSERVER_NAME;

						try {
							commRemote = (CommunicationRemote) Naming
									.lookup(objectURL);
						} catch (java.rmi.ConnectException e) {
							/*
							 * DEAD!
							 */
							System.err.println(objectURL + " not started jet");
							e.printStackTrace();
						} catch (MalformedURLException e) {
							e.printStackTrace();
						} catch (RemoteException e) {
							e.printStackTrace();
						} catch (NotBoundException e) {
							e.printStackTrace();
						}

						boolean live = true;
						while (live == true) {
							try {
								sleep(200);
								live = commRemote.isAlive();

							} catch (java.rmi.ConnectException e) {
								/*
								 * DEAD!
								 */
								live = false;

								// set dead on PlayersTable and refresh graphics
								player.setStatus(1);
								GameTableWindowSingleton.getInstance()
										.getGameTablePanel().refreshPanels(pt);

								// if (index == pt.getTurn()){
								// notifyTurnToAll(pt.getNextAlivePlayer(index).getPlayerId());
								// }
								return;
							} catch (RemoteException e) {
								e.printStackTrace();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}

					}
				};
				t.start();
			}
		}
	}

	private static CommunicationRemote bindToPlayer(Player player)
			throws Exception {
		CommunicationRemote commRemote = null;
		PlayerId nextPlayerId = player.getPlayerId();

		String objectURL = "rmi://" + nextPlayerId.getIpAddress() + ":"
				+ nextPlayerId.getPort() + "/"
				+ ConfigurationConstants.INFORMATIONSERVER_NAME;

		commRemote = (CommunicationRemote) Naming.lookup(objectURL);
		return commRemote;
	}

	public static void executePolling() {

		Thread t = new Thread() {
			@Override
			public void run() {
				// variables
				CommunicationRemote commRemote = null;
				List<PlayerId> PlayersList = InformationSingleton.getInstance()
						.getGame().getListOfPlayers();
				int myIndex = PlayerSelfSingleton.getInstance().getSelf()
						.getIndexInTable();

				PlayersTable pt = InformationSingleton.getInstance().getpt();
				Player nextPlayer = pt.getNextAlivePlayer(myIndex);
				PlayerId myself = PlayerSelfSingleton.getInstance().getSelf();
				// main cycle for ever and ever
				while (!(nextPlayer.getPlayerId().equals(myself))
						&& pt.getPenaltyForPlayer(myself) < pt
								.getMAX_PENALTIES()) {
					commRemote = null;
					if (nextPlayer == null) {
						// everyone is dead but me
					} else {
						try {
							commRemote = bindToPlayer(nextPlayer);
							boolean live = true;
							while (live == true
									&& pt.getPenaltyForPlayer(myself) < pt
											.getMAX_PENALTIES()) {
								sleep(200);
								live = commRemote.isAlive();
							}
						} catch (NoSuchElementException nsee) {
							nsee.printStackTrace();
						} catch (Exception e) {
							e.printStackTrace();
							nextPlayer = reactOnDeadPlayer(nextPlayer);
						}
						commRemote = null;
					}
				}
				/*
				 * i'm the only alive yet
				 */
			}
		};
		t.start();
	}

	private static Player reactOnDeadPlayer(Player nextPlayer) {
		/*
		 * DEAD!
		 */
		boolean live = false;
		CommunicationRemote commRemote = null;

		// set dead on PlayersTable and refresh graphics
		nextPlayer.setStatus(1);
		PlayersTable pt = InformationSingleton.getInstance().getpt();
		GameTableWindowSingleton.getInstance().getGameTablePanel()
				.refreshPanels(pt);

		int index = pt.getTurnForPlayer(nextPlayer);

		notifyStatus(index, false);
		nextPlayer = pt.getNextAlivePlayer(index);
		if (index == pt.getTurn()) {
			boolean nextAlive = false;
			while (!nextAlive) {
				// nextPlayer = pt.getNextAlivePlayer(index);

				commRemote = null;
				try {
					commRemote = bindToPlayer(nextPlayer);
					nextAlive = true;
				} catch (Exception e) {
					nextPlayer = reactOnDeadPlayer(nextPlayer);
					// index++;
				}
				// System.out.println("Bind executed to "
				// + nextPlayer.getPlayerId().getName());
				// try {
				// nextAlive = commRemote.isAlive();
				// System.out.println(nextPlayer.getPlayerId().getName()
				// + " passed aliveness");
				// } catch (Exception e1) {
				// System.out.println("nextPlayer before = "
				// + nextPlayer.getPlayerId().getName());
				// System.out.println("RIGA 501");
				// return reactOnDeadPlayer(nextPlayer);
				// }
			}
			notifyTurnToAll(nextPlayer.getPlayerId());
		}
		return nextPlayer;
	}
}
